//: \console -version=PRINT_JS1  -d

import
        tango.stdc.stringz,
        tango.sys.win32.UserGdi,
        tango.sys.Process,
        tango.sys.Environment,
        
        tango.io.stream.Lines,
        tango.io.device.Array ,
        tango.io.device.File ,
        tango.io.FilePath ,
        tango.io.FileScan,
        
        Path    = tango.io.Path ,
        Int     = tango.text.convert.Integer ,
        Utf     = tango.text.convert.Utf ,
        Txt     = tango.text.Util , 
        
        tango.core.Array, 
        
        tango.io.Console ,
        tango.io.Stdout;
extern(C){
        void exit(int);
        int memcpy( void*, void*, size_t );
}


struct CmdNode{
        char[]                          name ;
        void function(CmdNode*) cb ;

        static {
                const char[] php_ini    = `php.ini-` ;
                void run_empty(){
                        while(true){
                                Environment.cwd(INode.ExecDir) ;
                                INode.Load() ; 
                                bool isPhp              = Path.exists( INode.php_dir );
                                CmdNode[32]     list  ;
                                int     lindex          = 0 ;
                                CmdNode*                node ;
                                bool is_tmp_root        = false ;
                                if( isPhp ){
                                        if( INode.root_dir is  null){
                                                scope _php_dir  = new FilePath(  INode.php_dir ) ;
                                                INode.root_dir  = _php_dir.parent ;
                                                is_tmp_root     = true ;
                                        }
                                        Environment.cwd(  INode.php_dir  ) ;
                                }
                                scope(exit){
                                        if( is_tmp_root ){
                                                INode.root_dir = null ;
                                        }
                                }
                                
                                scope fs        = new FileScan ;
                                fs.sweep(".", (FilePath fp, bool isDir){
                                        if( lindex >= list.length - 4 ){
                                                return false ;
                                        }
                                        if( !isDir ){
                                                char[] _fp      = fp.file ;
                                                if( _fp.length > php_ini.length && _fp[0..php_ini.length] == php_ini ){
                                                        node            = &list[ lindex++ ] ;
                                                        node.name       =  fp.toString ;
                                                        node.cb         = &run_convert ;
                                                }
                                        }
                                        return isDir ? count(fp.toString, '/') < 3 : false ;
                                });
                                
                                
                                if( isPhp ){
                                        node            = &list[ lindex++ ] ;
                                        node.name       = "reduce size" ;
                                        node.cb = &run_lite ;
                                }
                                
                                node            = &list[ lindex++ ] ;
                                node.name       = "exit" ;
                                node.cb         = &run_exit ;
                                
                                for( int i =0 ; i < lindex; i++){
                                        node    = &list[i] ;
                                        Stdout("\t")( Int.toString(i+1) )(": \t ")( node.name )("\n");
                                }
                                Stdout("\t").flush;
                                
                                int i   = Int.parse( Cin.get ) - 1 ;
                                if( i < lindex && i >= 0 ){
                                        list[i].cb(&list[i]);
                                }
                                Stdout("\n").flush;
                        }
                }
                
                void run_convert(CmdNode* node){
                        convert_ini(node.name);
                }
                
                void run_lite(CmdNode* node){
                        if( LNode.list.length < 1 ){
                                Stdout("\t\tini node [ ")(LNode.node_name)(" ] is empty \n").flush;
                                return ;
                        }
                        scope char[][] remove_dirs , remove_files  ;
                        LNode*          ln;
                        scope           fs      = new FileScan ;
                        fs.sweep(".", (FilePath fp, bool isDir) {
                                char[] _path    = fp.toString[2..$];
                                ln                      = LNode.get( _path );
                                if( isDir ){
                                        if( ln is null ){
                                                remove_dirs     ~= _path ;
                                                return false ;
                                        }
                                        return ln.tp is LNode.Type.pDir ;
                                }
                                if( ln is null) {
                                        foreach(int i, ref n; LNode.list){
                                                if(  n.tp is LNode.Type.Star ){
                                                        if( _path.length >= n.name.length && _path[0..n.name.length] == n.name ){
                                                                return false ;
                                                        }
                                                }
                                        }
                                        if( fp.ext == ENode.ext_dl){
                                                foreach(int i, ref n; ENode.list){
                                                        if( fp.file == n.ext ){
                                                                return false ;
                                                        }
                                                }
                                        }
                                        remove_files    ~= _path;
                                }
                                return false ;
                        });
                        del_files(remove_files);
                        del_dirs(remove_dirs);
                }
                void run_exit(CmdNode* n){
                        exit(0);
                }
                
                private void del_files(T = char[])(T[] files){
                        foreach(ref file; files){
                                static if( is (T == FilePath) ){
                                        Path.remove(file.toString ) ;
                                }else{
                                        Path.remove(file) ;
                                }
                        }
                }
                
                private void del_dirs(char[][] dirs){
                        foreach(ref dir; dirs){
                                char[] _dir     =  Environment.toAbsolute(dir)  ;
                                foreach(ref c; _dir) if ( c == '/' ) c = '\\';
                                exec1("cmd /c rd /Q /S \"" ~_dir~ "\"" );
                        }
                }
                
        }
}


void main(){
        INode.Init ;
        CmdNode.run_empty() ;
}

struct VNode {
        char[256] _key, _val = 0 ;
        
        char[] key(){
                return fromStringz( _key.ptr );
        }
        
        char[] val(){
                return fromStringz( _val.ptr ) ;
        }
        
        void fill(Array bu, int flag){
                bu( key )("=")( val )("\n");
                Stdout.formatln("{}, {} = {}", flag, key, val );
        }
        
        void set(char[] key, char[] val){
                _key[]  = 0 ;
                _val[]  = 0 ;
                if( key !is null && key.length > 0  ){
                        memcpy(&_key[0], &key[0], key.length );
                }
                if( val !is null && val.length > 0  ){
                        memcpy(&_val[0], &val[0], val.length );
                }
        }
        
        bool getLine(char[] _line, int offset){
                char[] line     = Txt.trim( _line );
                if( line.length <= offset ){
                        // if(offset) Stdout.formatln("4: \t `{}`", _line);
                        return false ;
                }
                if( offset is 0 &&  line[0] is ';' ){
                        // if(offset) Stdout.formatln("3: \t `{}`", _line);
                        return false ;
                }
                if( offset !is 0 &&  line[0] !is ';' ){
                        // if(offset) Stdout.formatln("2: \t `{}`", _line);
                        return false ;
                }
                int i   = find(line, '=') ;
                if( i >= line.length ){
                        // if(offset) Stdout.formatln("1: \t `{}`", _line);
                        return false ;
                }
                char[] key_     = Txt.trimr( line[ offset .. i ] );
                if( key_[0] is ' ' ) key_       = key_[1..$] ;
                foreach(ref c; key_ ){
                        if( c is '=' || c is '\t' || c is  '[' || c is  ']' || c is  ' '  ){
                                // if(offset) Stdout.formatln("0: \t `{}`", _line);
                                return false ;
                        }
                }
                
                line            = Txt.trim( line[ i +1 .. $ ] );
                char[] val_     = Txt.trim( line[ 0 .. find(line, ';') ] );

                set(key_,  val_);
                
                // if(offset) Stdout.formatln(" \t `{}` \t\t\t {} \t\t\t {}", offset, key, val);
                
                return true ;
        }
}



struct ENode {
        static const char[] ext_key     = "extension" ;
        version(Windows){
                static const char[] ext_dl      = "dll" ;
        }else{
                static const char[] ext_dl      = "so" ;
        }
        char[]  ext ;
        bool            isDone ;
        
        static ENode*[] list    = [] ;
        
        void setExt(char[] _ext){
                ext     = "php_" ~ ( _ext.dup ) ~ "." ~ ext_dl ;
        }
        
        void fill(Array bu, int flag){
                bu( ext_key)("=")( ext )("\n");
                version(PRINT_JS) Stdout.formatln("\text_{} \t {} = {}", flag, ext_key, ext );
        }

        static bool getLine(Array bu, char[] _line, int offset){
                VNode v ;
                if( ! v.getLine(_line, offset) ){
                        return false ;
                }
                if( v.key != ext_key ){
                        return false ;
                }
                
                foreach( n ; list ){
                        if( n.ext == v.val ){
                                if( n.isDone ) {
                                        if( offset is 0 ){
                                                bu(";")( _line )("\n");
                                        }else{
                                                bu( _line )("\n");
                                        }
                                }else{
                                        n.isDone        = true ;
                                        n.fill(bu, offset);
                                }
                                return true ;
                        }
                }
                return false ;
        }
        
        
        static void dump(Array bu){
                Stdout("\n");
                bool has_write_help_content = false ;
                foreach( n ; list){
                        if( !n.isDone ){
                            Stdout("\text `")(n.ext)("` is not defiend \n");
                            if( !has_write_help_content ) {
                                 bu("; addtion extensions from phpini.ini\n");
                                has_write_help_content  = true ;    
                            }
                            bu("extension=")(n.ext)("\n");
                        }
                }
                Stdout.flush ;
        }
        
}

struct LNode{
        enum Type {
                None    = 0 ,
                File            = 1 ,
                Dir             = 1 << 1 ,
                pDir            = 1 << 2 ,
                Star            = 1 << 3 ,
        }
        Type    tp ;
        char[]  name ;
        static {
                const char[]    node_name       = "need_files" ;
                LNode*[]                list ;
                
                LNode* get(char[] _name, bool isCreate = false ){
                        foreach(int i, ref n ;list){
                                if( n.name == _name ){
                                        return n ;
                                }
                        }
                        if( !isCreate ){
                                return null ;
                        }
                        LNode*  n       = new LNode;
                        n.name          = _name.dup ;
                        n.tp                    = Type.None ;
                        list                    ~=      n ;
                        return n ;
                }
                
                void add(char[] ln){
                        
                        if( ln[0] is ';' || ln.length < 2 ){
                                return ;
                        }
                        Type tp = Type.File ;
                        if( ln[$-1] is '*' ){
                                tp      = Type.Star ;
                                ln      = ln[0..$-1] ;
                        }else if( ln[$-1] is '/' ){
                                tp      = Type.Dir ;
                                ln      = ln[0..$-1] ;
                        }
                        LNode* n        = get(ln, true);
                        if( n.tp is Type.None ){
                                n.tp    = tp ;
                        }else{
                                if( n.tp is Type.pDir ) {
                                        n.tp    = tp ;
                                } else {
                                        Stdout.formatln("dup {} in {}", ln, node_name );
                                        return ;
                                }
                        }
                        
                        for( int i = 1, len =  ln.length ; i <len  ; i++ ){
                                if( ln[i] is '/' ){
                                        LNode* _n               = get( ln[0..i], true);
                                        if( _n.tp == Type.None ){
                                                _n.tp   = Type.pDir ;
                                        }
                                }
                        }
                }
                
                void dump(){
                        foreach(int i, ref n; list){
                                Stdout.formatln(" {} => {}",  n.tp, n.name);
                        }
                }
        }
}

struct INode {
        static char[] root_var , root_dir, php_var, php_dir , php_ini_dir = null  ;
        
        char[]  key, val ;
        bool            isDone ;
        
        static const    top_key = 2 ;
        static char[]   ExecDir , StartDir, PHP_ini ;
        static Array    add_bu ;
        static INode*[] list            = [] ;
        
        static void Init(){
                StartDir                = Environment.cwd();
                if( StartDir[$-1] is '/' ) StartDir     = StartDir[0..$-1];
                wchar[250]   f;
                int i                   = GetModuleFileNameW(null, f.ptr, f.length) ;
                scope path      = Utf.toString( f[0..i] ) ;
                foreach(ref c ; path) if( c == '\\' ) c = '/' ;
                
                ExecDir         = path[0..rfind(path, '/') ].dup ;
                PHP_ini = path[ rfind(path, '/') + 1 .. rfind(path, '.') ].dup ~ ".ini" ;
                add_bu  = new Array(1024 , 1024 * 4 ) ;
        }
        
        static void Load(){
                char[]  php_ini = PHP_ini ;
                if( !Path.exists(php_ini) ){
                        php_ini = StartDir ~ "/" ~ PHP_ini ;
                }
                if( !Path.exists(php_ini) ){
                        Stdout("\t file `")(PHP_ini)("` is not exists.\n").flush;
                        Cin.get;
                        exit(1);
                }
                add_bu.clear ;
                root_var                = null ;
                root_dir                = null ;
                php_var         = null ;
                php_dir         = null ;
                php_ini_dir     = null ;
                scope(exit){
                        if( root_var is null ){
                                root_var        = `%&ROOT_DIR&%` ;
                        }
                        if( php_var is null ){
                                php_var = `%&PHP_DIR&%` ;
                        }
                }
                list.length             = 0 ;
                ENode.list.length       = 0 ;
                LNode.list.length       = 0 ;
                
                scope ini_da    = File.get(php_ini) ;
                scope ini_bu    = new Array(ini_da)  ;
                scope ls                = new Lines!(char)( ini_bu );
                
                void start_cb(char[] ln){
                        Stdout(ln)("\n");
                }
                
                void var_cb(char[] ln){
                        if( ln[0] is ';' ) return ;
                        int     i               = find(ln, '=');
                        if(  i == ln.length ) return ;
                        char[] _key     = Txt.trim(ln[ 0 .. i ]) ;
                        char[] _val             = Txt.trim(ln[  i +1 .. $ ]) ;
                        if( _key == "root_var" ){
                                root_var        = _val.dup ;
                        }else if( _key == "root_dir" ){
                                if( _val != "null" ){
                                        version(Windows){
                                                foreach(ref c ; _val ) if( c is '\\' ) c = '/' ;
                                        }
                                        if( _val.length > 1 && _val[$-1] is '/' ){
                                                root_dir        = _val[0..$-1].dup ;
                                        }else{
                                                root_dir        = _val.dup ;
                                        }
                                }
                        }else  if( _key == "php_var" ){
                                php_var = _val.dup ;
                        }else  if( _key == "php_dir" ){
                                if( _val.length > 1 ){
                                        if( _val[1] is ':' ||  _val[0] is '/' ){
                                                php_dir = _val.dup ;
                                        }else{
                                                php_dir =  ExecDir ~  '/' ~ _val.dup ;
                                        }
                                }
                        }else if( _key == "php_ini_dir" ){
                                if( _val.length > 1 ){
                                        php_ini_dir     = _val.dup ;
                                }
                        }else{
                                Stdout("undefiend var `")(ln)("`\n");
                        }
                }
                void php_cb(char[] ln){
                        int     i               = find(ln, '=');
                        if(  i == ln.length ) return ;
                        char[] _key     = Txt.trim(ln[ 0 .. i ]) ;
                        char[] _val             = Txt.trim(ln[  i +1 .. $ ]) ;
                        foreach( _n ; list ){
                                if( _n.key == _key ){
                                        Stdout("dup key:")(_key)("\n");
                                        return  ;
                                }
                        }
                        INode* p        = new INode ;
                        p.key           = _key.dup ;
                        p.val           = _val.dup ;
                        list            ~= p ;
                }
                void extension_cb(char[] ln){
                        if( ln[0] is ';' ) return ;
                        ENode* ptr      = new ENode ;
                        ptr.setExt(ln) ;
                        foreach( _n ; ENode.list ){
                                if( _n.ext == ptr.ext ){
                                        return  ;
                                }
                        }
                        ENode.list      ~= ptr ;
                }
                
                void lite_cb(char[] ln){
                        LNode.add(ln);
                }
                void add_cb(char[] ln){
                        add_bu(ln)("\n");
                }
                void delegate(char[]) ln_cb  = &start_cb ;
                
                foreach(ref char[] _ln; ls){
                        auto ln        = Txt.trim(_ln) ;
                        if( ln.length < 1 ) continue ;
                        if( ln[0] is '[' && ln[$-1] is ']' ){
                                if( ln.length > 2 ){
                                        ln      = Txt.trim(_ln[1..$-1]) ;
                                        if( ln == "var" ){
                                                ln_cb   = &var_cb ;
                                        }else if( ln == "php" ){
                                                ln_cb   = &php_cb ;
                                        }else if( ln == "extension" ){
                                                ln_cb   = &extension_cb ;
                                        }else if( ln == LNode.node_name ){
                                                ln_cb   = &lite_cb ;
                                        }else{
                                                ln_cb   = &add_cb ;
                                                add_bu("\n[")(ln)("]\n");
                                        }
                                }
                                continue;
                        }
                        ln_cb( ln );
                }
        }

        static bool getLine(Array bu, char[] _line, int offset){
                VNode v ;
                if( ! v.getLine(_line, offset) ){
                        return false ;
                }
                foreach( n ; list ){
                        if( n.key == v.key ){
                                if( n.isDone ) {
                                        if( offset is 0 ){
                                                bu(";")( _line )("\n");
                                        }else{
                                                bu( _line )("\n");
                                        }
                                }else{
                                        n.isDone        = true ;
                                        n.fill(bu, offset);
                                }
                                return true ;
                        }
                }
                return false ;
        }
        
        static void dump(Array bu){
                foreach( n ; list){
                        if( !n.isDone ){
                                n.fill(bu, 2);
                        }
                }
        }

        void fill(Array bu, int flag){
                bu( key )(" = ")( val )("\n");
                version(PRINT_JS) Stdout.formatln("\tnode_{} \t {} = {}", flag, key, val );
        }

}

void copy_ini(alias Node, int offset)(Array b1,Array b2){
        b2.clear ;
        auto ls = new Lines!(char)(b1);
        foreach( _line ; ls ){
                if( !Node.getLine(b2 ,  _line, offset ) ){
                        b2( _line )("\n");
                }
        }
}

void convert_ini(char[] _file ){
        if( !Path.exists(_file) ){
                Stdout("\t\t file `")(_file)("` is not exists.\n").flush;
                return ;
        }
        int buf_size    = Path.fileSize( _file )   ; 
        int buf_size2   = buf_size + 2048 + INode.add_bu.readable ; 

        Array bu1       = new Array(buf_size2, buf_size2);
        Array bu2       = new Array(buf_size2, buf_size2);
        ubyte[] _bu2    =  ( cast(ubyte*) &( bu2("1").slice[0] ) )[ 0 .. buf_size2 ];
        
        bu1.clear;
        bu2.clear;
        
        auto file       = new File(_file);
        int i           = file.read( _bu2 );
        file.close;
        if( i > buf_size || i < 0 ){
                Stdout("\t\t read `")(_file)("` error.\n").flush;
                exit(2);
        }
        bu1( _bu2[0..i] );
        
        copy_ini!(INode, 0)(bu1, bu2);
        copy_ini!(INode, 1)(bu2, bu1);
        
        copy_ini!(ENode, 0)(bu1, bu2);
        copy_ini!(ENode, 1)(bu2, bu1);
        
        INode.dump( bu1 );
        ENode.dump( bu1 );
        
        
        bu1("\n; add by ")( INode.PHP_ini )("\n")( INode.add_bu.slice );
        
        char[] _da      = cast(char[]) bu1.slice ;
        if( INode.root_dir ){
                _da     = Txt.substitute(_da, INode.root_var, INode.root_dir);
        }
        if( INode.php_dir ){
                _da     = Txt.substitute(_da, INode.php_var, INode.php_dir );
        }
        File.set(`php.ini`,  _da);
        if( Path.exists(INode.php_ini_dir) ){
                File.set(INode.php_ini_dir ~ `\php.ini`, _da);
        }
}



void exec1(char[] cmd ){
        char[][char[]] env              = Environment.get();
        
        char[] exec_cmd ;
        for(int i = 0 ; i < cmd.length; i++){
                if( cmd[i] == ' ' || cmd[i] == '\t' || cmd[i] == '\n' ){
                        exec_cmd        = cmd[ 0 .. i ].dup ;
                        cmd                     = cmd[ i+1 .. $].dup ;
                        break;
                }                       
        }
        
        static const char[][] exe_ext   = ["", ".exe", ".cmd" ];
        
        char[] exec_file        = null ;
        
        
        scope dirs              = Txt.split(env["Path"] , `;`);
        L1:
        foreach( ext; exe_ext ) foreach( dir; dirs){
                if( dir.length < 2 ){
                        continue;
                }
                scope path      = new FilePath( dir ~ `\` ~ exec_cmd ~ ext );
                if( path.exists ){
                        exec_file       = path.toString;
                        break L1;
                }
        }
        
        if( exec_file is null ){
                Stdout("Can't find  command `")( exec_file)("` in PATH `")( env["Path"] )("`" ).flush;
                return ;
        }
        Stdout(exec_file)(" ")(cmd)("\n").flush;

        try {
                exec0(exec_file, cmd, env);
        }catch (Exception e){
                Stdout("- {}")(e.msg)("\n").flush;
        }
}

void exec0(char[] dmd, char[] cmd, char[][char[]] _env){
        char[]  env     = toNullEndedBuffer(_env);
        wchar*  pFile   = toString16z( Utf.toString16(dmd) );
        
        DWORD flags     =       CREATE_NEW_PROCESS_GROUP;
        
        STARTUPINFO si;
        si.cb                   = STARTUPINFO.sizeof;
        si.dwFlags              = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
        
        si.hStdInput    = GetStdHandle(STD_INPUT_HANDLE) ;
        si.hStdOutput   = GetStdHandle(STD_OUTPUT_HANDLE) ;
        si.hStdError            = GetStdHandle(STD_ERROR_HANDLE);

        PROCESS_INFORMATION pi;
        auto ret = CreateProcessW(
                          pFile,
                          toString16z( Utf.toString16(cmd) ),
                          null, null,   //LPSECURITY_ATTRIBUTES
                          TRUE,
                          flags ,
                          env.ptr, //环境
                          null, //目录
                          &si, 
                          &pi);
        ret     = WaitForSingleObject(pi.hProcess, INFINITE);
        if( WAIT_OBJECT_0 != ret ){
                TerminateProcess(pi.hProcess, 1);
        }else{
                uint exitcode;
                GetExitCodeProcess(pi.hProcess, &exitcode);
        }
}

char[] toNullEndedBuffer(char[][char[]] src){
        char[] dest;
        foreach (key, value; src){
                dest ~= key ~ '=' ~ value ~ '\0';
        }
        dest ~= '\0';
        return dest;
}
